#include <dirent.h>
#include <stdlib.h>
#include <sys/time.h>

#include <iostream>
#include <regex>

#include "AclLiteApp.h"
#include "AclLiteResource.h"
#include "AclLiteThread.h"
#include "AclLiteType.h"
#include "AclLiteUtils.h"
#include "CarParams.h"
#include "VideoCapture.h"
#include "detectPostprocess.h"
#include "detectPreprocess.h"
#include "inference.h"

#define NUM 1
#define INVALID -1
using namespace std;

namespace {
uint32_t kExitCount = 0;
const char* kConfigFile = "../scripts/params.conf";
const string kRegexDeviceNum = "^device_num+$";
const string kRegexRtspNumPerDevice = "^RtspNumPerDevice+$";
aclrtContext context;
aclrtRunMode runMode;
vector<aclrtContext> kContext;
}  // namespace

int MainThreadProcess(uint32_t msgId, shared_ptr<void> msgData,
                      void* userData) {
  if (msgId == MSG_APP_EXIT) {
    kExitCount--;
  }
  if (!kExitCount) {
    AclLiteApp& app = GetAclLiteAppInstance();
    app.WaitEnd();
    ACLLITE_LOG_INFO("Receive exit message, exit now");
  }
  return ACLLITE_OK;
}

AclLiteError ParseConfig(uint32_t& deviceNum, uint32_t& rtspNumPerDevice) {
  map<string, string> config;
  if (!ReadConfig(config, kConfigFile)) {
    return ACLLITE_ERROR;
  }

  regex deviceNumRegex(kRegexDeviceNum.c_str());
  regex RtspNumPerDeviceRegex(kRegexRtspNumPerDevice.c_str());
  map<string, string>::const_iterator mIter = config.begin();
  for (; mIter != config.end(); ++mIter) {
    if (regex_match(mIter->first, deviceNumRegex)) {
      deviceNum = stoi(mIter->second);
      ACLLITE_LOG_INFO("Data config item: %s=%s", mIter->first.c_str(),
                       mIter->second.c_str());
    } else if (regex_match(mIter->first, RtspNumPerDeviceRegex)) {
      rtspNumPerDevice = stoi(mIter->second);
      ACLLITE_LOG_INFO("Data config item: %s=%s", mIter->first.c_str(),
                       mIter->second.c_str());
    }
  }
  return ACLLITE_OK;
}

void CreateInstances(vector<AclLiteThreadParam>& threadTbl, int32_t deviceId,
                     aclrtContext& context, aclrtRunMode& runMode,
                     int rtspNumPerDevice) {
  AclLiteThreadParam detectPreThreadParam;
  for (int index = 0; index < rtspNumPerDevice; index++) {
    detectPreThreadParam.threadInst = new DetectPreprocessThread(
        kConfigFile, deviceId, deviceId * rtspNumPerDevice + index, runMode);
    detectPreThreadParam.threadInstName.assign(
        kDetectPreName[deviceId * rtspNumPerDevice + index].c_str());
    detectPreThreadParam.context = context;
    detectPreThreadParam.runMode = runMode;
    threadTbl.push_back(detectPreThreadParam);

    AclLiteThreadParam detectPostThreadParam;
    detectPostThreadParam.threadInst = new DetectPostprocessThread();
    detectPostThreadParam.threadInstName.assign(
        kDetectPostName[deviceId * rtspNumPerDevice + index].c_str());
    detectPostThreadParam.context = context;
    detectPostThreadParam.runMode = runMode;
    threadTbl.push_back(detectPostThreadParam);
  }

  AclLiteThreadParam InferParam;
  InferParam.threadInst = new InferenceThread(runMode);
  InferParam.threadInstName.assign(kInferName[deviceId].c_str());
  InferParam.context = context;
  InferParam.runMode = runMode;
  threadTbl.push_back(InferParam);
}

void CreateThreadInstance(vector<AclLiteThreadParam>& threadTbl,
                          AclLiteResource& aclDev) {
  uint32_t deviceNum;
  uint32_t rtspNumPerDevice;
  runMode = aclDev.GetRunMode();

  AclLiteError ret = ParseConfig(deviceNum, rtspNumPerDevice);
  if (ret != ACLLITE_OK) {
    return;
  }
  kExitCount = deviceNum * rtspNumPerDevice;

  for (int32_t i = 0; i < deviceNum; i++) {
    ret = aclrtSetDevice(i);
    if (ret != ACL_ERROR_NONE) {
      ACLLITE_LOG_ERROR("Acl open device %d failed", i);
      return;
    }
    ret = aclrtCreateContext(&context, i);
    if (ret != ACL_ERROR_NONE) {
      ACLLITE_LOG_ERROR("Create acl context failed, error:%d", ret);
      return;
    }
    kContext.push_back(context);
    CreateInstances(threadTbl, i, context, runMode, rtspNumPerDevice);
  }
}

void ExitApp(AclLiteApp& app, vector<AclLiteThreadParam>& threadTbl) {
  for (int i = 0; i < threadTbl.size(); i++) {
    aclrtSetCurrentContext(threadTbl[i].context);
    delete threadTbl[i].threadInst;
  }

  app.Exit();

  for (int i = 0; i < kContext.size(); i++) {
    aclrtDestroyContext(kContext[i]);
    aclrtResetDevice(i);
  }
}

void StartApp(AclLiteResource& aclDev) {
  vector<AclLiteThreadParam> threadTbl;
  CreateThreadInstance(threadTbl, aclDev);
  AclLiteApp& app = CreateAclLiteAppInstance();
  AclLiteError ret = app.Start(threadTbl);
  if (ret != ACLLITE_OK) {
    ACLLITE_LOG_ERROR("Start app failed, error %d", ret);
    ExitApp(app, threadTbl);
    return;
  }

  for (int i = 0; i < threadTbl.size(); i++) {
    ret = SendMessage(threadTbl[i].threadInstId, MSG_APP_START, nullptr);
  }

  app.Wait(MainThreadProcess, nullptr);
  ExitApp(app, threadTbl);

  return;
}

int main(int argc, char* argv[]) {
  AclLiteResource aclDev = AclLiteResource();
  AclLiteError ret = aclDev.Init();
  if (ret != ACLLITE_OK) {
    ACLLITE_LOG_ERROR("Init app failed");
  }

  StartApp(aclDev);

  return ACLLITE_OK;
}
